1   /*
2    * Copyright (c) 2000, 2008, Oracle and/or its affiliates. All rights reserved.
3    * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4    *
5    * This code is free software; you can redistribute it and/or modify it
6    * under the terms of the GNU General Public License version 2 only, as
7    * published by the Free Software Foundation.  Oracle designates this
8    * particular file as subject to the "Classpath" exception as provided
9    * by Oracle in the LICENSE file that accompanied this code.
10   *
11   * This code is distributed in the hope that it will be useful, but WITHOUT
12   * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13   * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14   * version 2 for more details (a copy is included in the LICENSE file that
15   * accompanied this code).
16   *
17   * You should have received a copy of the GNU General Public License version
18   * 2 along with this work; if not, write to the Free Software Foundation,
19   * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20   *
21   * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22   * or visit www.oracle.com if you need additional information or have any
23   * questions.
24   */
25  
26  package javax.management.openmbean;
27  
28  import com.sun.jmx.mbeanserver.GetPropertyAction;
29  import java.io.IOException;
30  import java.io.InvalidObjectException;
31  import java.io.ObjectInputStream;
32  import java.io.Serializable;
33  import java.security.AccessController;
34  import java.security.PrivilegedAction;
35  import java.util.Arrays;
36  import java.util.Collections;
37  import java.util.List;
38  import javax.management.Descriptor;
39  import javax.management.ImmutableDescriptor;
40  
41  /**
42   * The <code>OpenType</code> class is the parent abstract class of all classes which describe the actual <i>open type</i>
43   * of open data values.
44   * <p>
45   * An <i>open type</i> is defined by:
46   * <ul>
47   *  <li>the fully qualified Java class name of the open data values this type describes;
48   *      note that only a limited set of Java classes is allowed for open data values
49   *      (see {@link #ALLOWED_CLASSNAMES_LIST ALLOWED_CLASSNAMES_LIST}),</li>
50   *  <li>its name,</li>
51   *  <li>its description.</li>
52   * </ul>
53   *
54   * @param <T> the Java type that instances described by this type must
55   * have.  For example, {@link SimpleType#INTEGER} is a {@code
56   * SimpleType<Integer>} which is a subclass of {@code OpenType<Integer>},
57   * meaning that an attribute, parameter, or return value that is described
58   * as a {@code SimpleType.INTEGER} must have Java type
59   * {@link Integer}.
60   *
61   * @since 1.5
62   */
63  public abstract class OpenType<T> implements Serializable {
64  
65      /* Serial version */
66      static final long serialVersionUID = -9195195325186646468L;
67  
68  
69      /**
70       * List of the fully qualified names of the Java classes allowed for open
71       * data values. A multidimensional array of any one of these classes or
72       * their corresponding primitive types is also an allowed class for open
73       * data values.
74       *
75         <pre>ALLOWED_CLASSNAMES_LIST = {
76          "java.lang.Void",
77          "java.lang.Boolean",
78          "java.lang.Character",
79          "java.lang.Byte",
80          "java.lang.Short",
81          "java.lang.Integer",
82          "java.lang.Long",
83          "java.lang.Float",
84          "java.lang.Double",
85          "java.lang.String",
86          "java.math.BigDecimal",
87          "java.math.BigInteger",
88          "java.util.Date",
89          "javax.management.ObjectName",
90          CompositeData.class.getName(),
91          TabularData.class.getName() } ;
92         </pre>
93       *
94       */
95      public static final List<String> ALLOWED_CLASSNAMES_LIST =
96        Collections.unmodifiableList(
97          Arrays.asList(
98            "java.lang.Void",
99            "java.lang.Boolean",
100           "java.lang.Character",
101           "java.lang.Byte",
102           "java.lang.Short",
103           "java.lang.Integer",
104           "java.lang.Long",
105           "java.lang.Float",
106           "java.lang.Double",
107           "java.lang.String",
108           "java.math.BigDecimal",
109           "java.math.BigInteger",
110           "java.util.Date",
111           "javax.management.ObjectName",
112           CompositeData.class.getName(),        // better refer to these two class names like this, rather than hardcoding a string,
113           TabularData.class.getName()) );       // in case the package of these classes should change (who knows...)
114 
115 
116     /**
117      * @deprecated Use {@link #ALLOWED_CLASSNAMES_LIST ALLOWED_CLASSNAMES_LIST} instead.
118      */
119     @Deprecated
120     public static final String[] ALLOWED_CLASSNAMES =
121         ALLOWED_CLASSNAMES_LIST.toArray(new String[0]);
122 
123 
124     /**
125      * @serial The fully qualified Java class name of open data values this
126      *         type describes.
127      */
128     private String className;
129 
130     /**
131      * @serial The type description (should not be null or empty).
132      */
133     private String description;
134 
135     /**
136      * @serial The name given to this type (should not be null or empty).
137      */
138     private String typeName;
139 
140     /**
141      * Tells if this type describes an array (checked in constructor).
142      */
143     private transient boolean isArray = false;
144 
145     /**
146      * Cached Descriptor for this OpenType, constructed on demand.
147      */
148     private transient Descriptor descriptor;
149 
150     /* *** Constructor *** */
151 
152     /**
153      * Constructs an <code>OpenType</code> instance (actually a subclass instance as <code>OpenType</code> is abstract),
154      * checking for the validity of the given parameters.
155      * The validity constraints are described below for each parameter.
156      * <br>&nbsp;
157      * @param  className  The fully qualified Java class name of the open data values this open type describes.
158      *                    The valid Java class names allowed for open data values are listed in
159      *                    {@link #ALLOWED_CLASSNAMES_LIST ALLOWED_CLASSNAMES_LIST}.
160      *                    A multidimensional array of any one of these classes
161      *                    or their corresponding primitive types is also an allowed class,
162      *                    in which case the class name follows the rules defined by the method
163      *                    {@link Class#getName() getName()} of <code>java.lang.Class</code>.
164      *                    For example, a 3-dimensional array of Strings has for class name
165      *                    &quot;<code>[[[Ljava.lang.String;</code>&quot; (without the quotes).
166      * <br>&nbsp;
167      * @param  typeName  The name given to the open type this instance represents; cannot be a null or empty string.
168      * <br>&nbsp;
169      * @param  description  The human readable description of the open type this instance represents;
170      *                      cannot be a null or empty string.
171      * <br>&nbsp;
172      * @throws IllegalArgumentException  if <var>className</var>, <var>typeName</var> or <var>description</var>
173      *                                   is a null or empty string
174      * <br>&nbsp;
175      * @throws OpenDataException  if <var>className</var> is not one of the allowed Java class names for open data
176      */
177     protected OpenType(String  className,
178                        String  typeName,
179                        String  description) throws OpenDataException {
180         checkClassNameOverride();
181         this.typeName = valid("typeName", typeName);
182         this.description = valid("description", description);
183         this.className = validClassName(className);
184         this.isArray = (this.className != null && this.className.startsWith("["));
185     }
186 
187     /* Package-private constructor for callers we trust to get it right. */
188     OpenType(String className, String typeName, String description,
189              boolean isArray) {
190         this.className   = valid("className",className);
191         this.typeName    = valid("typeName", typeName);
192         this.description = valid("description", description);
193         this.isArray     = isArray;
194     }
195 
196     private void checkClassNameOverride() throws SecurityException {
197         if (this.getClass().getClassLoader() == null)
198             return;  // We trust bootstrap classes.
199         if (overridesGetClassName(this.getClass())) {
200             final GetPropertyAction getExtendOpenTypes =
201                 new GetPropertyAction("jmx.extend.open.types");
202             if (AccessController.doPrivileged(getExtendOpenTypes) == null) {
203                 throw new SecurityException("Cannot override getClassName() " +
204                         "unless -Djmx.extend.open.types");
205             }
206         }
207     }
208 
209     private static boolean overridesGetClassName(final Class<?> c) {
210         return AccessController.doPrivileged(new PrivilegedAction<Boolean>() {
211             public Boolean run() {
212                 try {
213                     return (c.getMethod("getClassName").getDeclaringClass() !=
214                             OpenType.class);
215                 } catch (Exception e) {
216                     return true;  // fail safe
217                 }
218             }
219         });
220     }
221 
222     private static String validClassName(String className) throws OpenDataException {
223         className   = valid("className", className);
224 
225         // Check if className describes an array class, and determines its elements' class name.
226         // (eg: a 3-dimensional array of Strings has for class name: "[[[Ljava.lang.String;")
227         //
228         int n = 0;
229         while (className.startsWith("[", n)) {
230             n++;
231         }
232         String eltClassName; // class name of array elements
233         boolean isPrimitiveArray = false;
234         if (n > 0) {
235             if (className.startsWith("L", n) && className.endsWith(";")) {
236                 // removes the n leading '[' + the 'L' characters
237                 // and the last ';' character
238                 eltClassName = className.substring(n+1, className.length()-1);
239             } else if (n == className.length() - 1) {
240                 // removes the n leading '[' characters
241                 eltClassName = className.substring(n, className.length());
242                 isPrimitiveArray = true;
243             } else {
244                 throw new OpenDataException("Argument className=\"" + className +
245                         "\" is not a valid class name");
246             }
247         } else {
248             // not an array
249             eltClassName = className;
250         }
251 
252         // Check that eltClassName's value is one of the allowed basic data types for open data
253         //
254         boolean ok = false;
255         if (isPrimitiveArray) {
256             ok = ArrayType.isPrimitiveContentType(eltClassName);
257         } else {
258             ok = ALLOWED_CLASSNAMES_LIST.contains(eltClassName);
259         }
260         if ( ! ok ) {
261             throw new OpenDataException("Argument className=\""+ className +
262                                         "\" is not one of the allowed Java class names for open data.");
263         }
264 
265         return className;
266     }
267 
268     /* Return argValue.trim() provided argValue is neither null nor empty;
269        otherwise throw IllegalArgumentException.  */
270     private static String valid(String argName, String argValue) {
271         if (argValue == null || (argValue = argValue.trim()).equals(""))
272             throw new IllegalArgumentException("Argument " + argName +
273                                                " cannot be null or empty");
274         return argValue;
275     }
276 
277     /* Package-private access to a Descriptor containing this OpenType. */
278     synchronized Descriptor getDescriptor() {
279         if (descriptor == null) {
280             descriptor = new ImmutableDescriptor(new String[] {"openType"},
281                                                  new Object[] {this});
282         }
283         return descriptor;
284     }
285 
286     /* *** Open type information methods *** */
287 
288     /**
289      * Returns the fully qualified Java class name of the open data values
290      * this open type describes.
291      * The only possible Java class names for open data values are listed in
292      * {@link #ALLOWED_CLASSNAMES_LIST ALLOWED_CLASSNAMES_LIST}.
293      * A multidimensional array of any one of these classes or their
294      * corresponding primitive types is also an allowed class,
295      * in which case the class name follows the rules defined by the method
296      * {@link Class#getName() getName()} of <code>java.lang.Class</code>.
297      * For example, a 3-dimensional array of Strings has for class name
298      * &quot;<code>[[[Ljava.lang.String;</code>&quot; (without the quotes),
299      * a 3-dimensional array of Integers has for class name
300      * &quot;<code>[[[Ljava.lang.Integer;</code>&quot; (without the quotes),
301      * and a 3-dimensional array of int has for class name
302      * &quot;<code>[[[I</code>&quot; (without the quotes)
303      *
304      * @return the class name.
305      */
306     public String getClassName() {
307         return className;
308     }
309 
310     // A version of getClassName() that can only be called from within this
311     // package and that cannot be overridden.
312     String safeGetClassName() {
313         return className;
314     }
315 
316     /**
317      * Returns the name of this <code>OpenType</code> instance.
318      *
319      * @return the type name.
320      */
321     public String getTypeName() {
322 
323         return typeName;
324     }
325 
326     /**
327      * Returns the text description of this <code>OpenType</code> instance.
328      *
329      * @return the description.
330      */
331     public String getDescription() {
332 
333         return description;
334     }
335 
336     /**
337      * Returns <code>true</code> if the open data values this open
338      * type describes are arrays, <code>false</code> otherwise.
339      *
340      * @return true if this is an array type.
341      */
342     public boolean isArray() {
343 
344         return isArray;
345     }
346 
347     /**
348      * Tests whether <var>obj</var> is a value for this open type.
349      *
350      * @param obj the object to be tested for validity.
351      *
352      * @return <code>true</code> if <var>obj</var> is a value for this
353      * open type, <code>false</code> otherwise.
354      */
355     public abstract boolean isValue(Object obj) ;
356 
357     /**
358      * Tests whether values of the given type can be assigned to this open type.
359      * The default implementation of this method returns true only if the
360      * types are equal.
361      *
362      * @param ot the type to be tested.
363      *
364      * @return true if {@code ot} is assignable to this open type.
365      */
366     boolean isAssignableFrom(OpenType<?> ot) {
367         return this.equals(ot);
368     }
369 
370     /* *** Methods overriden from class Object *** */
371 
372     /**
373      * Compares the specified <code>obj</code> parameter with this
374      * open type instance for equality.
375      *
376      * @param obj the object to compare to.
377      *
378      * @return true if this object and <code>obj</code> are equal.
379      */
380     public abstract boolean equals(Object obj) ;
381 
382     public abstract int hashCode() ;
383 
384     /**
385      * Returns a string representation of this open type instance.
386      *
387      * @return the string representation.
388      */
389     public abstract String toString() ;
390 
391     /**
392      * Deserializes an {@link OpenType} from an {@link java.io.ObjectInputStream}.
393      */
394     private void readObject(ObjectInputStream in)
395             throws IOException, ClassNotFoundException {
396         checkClassNameOverride();
397         ObjectInputStream.GetField fields = in.readFields();
398         final String classNameField;
399         final String descriptionField;
400         final String typeNameField;
401         try {
402             classNameField =
403                 validClassName((String) fields.get("className", null));
404             descriptionField =
405                 valid("description", (String) fields.get("description", null));
406             typeNameField =
407                 valid("typeName", (String) fields.get("typeName", null));
408         } catch (Exception e) {
409             IOException e2 = new InvalidObjectException(e.getMessage());
410             e2.initCause(e);
411             throw e2;
412         }
413         className = classNameField;
414         description = descriptionField;
415         typeName = typeNameField;
416         isArray = (className.startsWith("["));
417     }
418 }